package com.googlecode.jot;

import java.lang.reflect.Type;
import java.util.Collection;
import java.util.LinkedList;

/**
 * The {@code TranslationContext} translates objects
 * using {@link TypeTranslator}s.
 *
 */
public class TranslationContext {

	private Collection<TypeTranslator<?, ?>> typeTranslators
		= new LinkedList<TypeTranslator<?, ?>>();

	private boolean fastNullConversions = true;

	/**
	 * Translates the given local to a foreign.
	 * @param <Foreign> the foreign type
	 * @param <Local> the local type
	 * @param local the local
	 * @return the foreign
	 */
	@SuppressWarnings("unchecked")
	public <Foreign, Local> Foreign translateFromLocal(Local local) {
		return (Foreign)translateFromLocal((Local)local, new TranslationHints());
	}

	/**
	 * Translates the given local to a foreign.
	 * @param <Foreign> the foreign type
	 * @param <Local> the local type
	 * @param local the local
	 * @param hints the {@link TranslationHints}
	 * @return the foreign
	 */
	@SuppressWarnings("unchecked")
	public <Foreign, Local> Foreign translateFromLocal(Local local, TranslationHints hints) {
		// bail fast on nulls
		if (local==null && fastNullConversions) {
			return null;
		}

		// the converter we'll be using
		TypeTranslator<Foreign, Local> trans
			= (TypeTranslator<Foreign, Local>)getTypeTranslator(local.getClass());

		// if we still don't have a converter then lets bail now
		if (trans==null) {
			throw new TranslationException(
				"Unable to translate "+local.getClass().getName());
		}

		// convert it
		return trans.translateFromLocal(local, this, hints);
	}

	/**
	 * Translates the given foreign to a local.
	 * @param <Foreign> the foreign type
	 * @param <Local> the local type
	 * @param foreign the foreign
	 * @param localType the desired local type
	 * @return the local
	 */
	@SuppressWarnings("unchecked")
	public <Foreign, Local> Local translateToLocal(Foreign foreign, Type localType) {
		return (Local)translateToLocal((Foreign)foreign, localType, new TranslationHints());
	}

	/**
	 * Translates the given foreign to a local.
	 * @param <Foreign> the foreign type
	 * @param <Local> the local type
	 * @param foreign the foreign
	 * @param localType the desired local type
	 * @param hints the {@link TranslationHints}
	 * @return the local
	 */
	@SuppressWarnings("unchecked")
	public <Foreign, Local> Local translateToLocal(Foreign foreign, Type localType, TranslationHints hints) {
		// bail fast on nulls
		if (foreign==null && fastNullConversions) {
			return null;
		}

		// the converter we'll be using
		TypeTranslator<Foreign, Local> trans
			= (TypeTranslator<Foreign, Local>)getTypeTranslator(localType);

		// if we still don't have a converter then lets bail now
		if (trans==null) {
			throw new TranslationException(
				"Unable to translate "+localType);
		}

		// convert it
		return trans.translateToLocal(foreign, this, localType, hints);
	}


	/**
	 * Registers the given {@link TypeTranslator}.
	 * @param translator the {@link TypeTranslator}
	 */
	public void registerTranslator(TypeTranslator<?, ?> translator) {
		if (!typeTranslators.contains(translator)) {
			typeTranslators.add(translator);
		}
	}

	/**
	 * Returns the configured {@link TypeTranslator} for
	 * the given local {@link Class}.
	 * @param localType the type for translating.
	 * @return the {@link TypeTranslator}
	 */
	public TypeTranslator<?, ?> getTypeTranslator(Type localType) {
		for (TypeTranslator<?, ?> tt : typeTranslators) {
			if (tt.supportsLocal(localType)) {
				return tt;
			}
		}
		return null;
	}

	/**
	 * Unregisters the given {@link TypeTranslator}.
	 * @param translator the {@link TypeTranslator}
	 */
	public void unregisterTranslator(TypeTranslator<?, ?> translator) {
		typeTranslators.remove(translator);
	}

	/**
	 * Unregisters all {@link TypeTranslator}s.
	 */
	public void unregisterAllTranslators() {
		typeTranslators.clear();
	}

	/**
	 * @param fastNullConversions the fastNullConversions to set
	 */
	public void setFastNullConversions(boolean fastNullConversions) {
		this.fastNullConversions = fastNullConversions;
	}

}
